/*
Copyright (c) 2010 Andreas Bjerkeholt
License: MIT
http://www.opensource.org/licenses/mit-license.php
*/

/*
This is a custom mixer implementation written to work with Dingoo-SDK.
It requires tremor with extensions for allowing playback from a buffer
(included with the SDK).
*/
#ifdef SML
#include "mixer.h"

waveout_inst* woHandle;

OS_STK   TaskStk[NO_TASKS][TASK_STK_SIZE];      // Tasks stacks 
OS_STK   TaskStartStk[TASK_STK_SIZE];
char     TaskData[NO_TASKS];                    // Parameters to pass to each task

volatile int curReadBuffer;
int curWriteBuffer;
static volatile int soundThreadPause;
static volatile int soundThreadExit;

playingSound ps;

FILE* oggFileHandle;
OggVorbis_File* oggFile = NULL;
int oggCurrentSection;
bool oggEOF = true;
char oggBuffer[MAXFRAGSIZE];
int oggBufferUnWritten = 0;
int oggBufferLastWriteLength = 0;

vorbis_info* oggInfo;
uint32_t oggSongLength = 0;
uint32_t oggSongCur = 0;

uint32_t mixerSampleRate;
uint8_t mixerBits;
uint8_t mixerChannels;
int8_t mixerVolume = 20;

bool mixerStarted = false;

char _lastError[256] = {0};


static unsigned char mixerSoundBufferTotal[SOUNDBUFFERS * MAXFRAGSIZE];  // Sound buffer
void *mixerSoundBuffers[SOUNDBUFFERS] = {                                // Sound buffers
   (void *)(mixerSoundBufferTotal + ((MAXFRAGSIZE * 0))),
   (void *)(mixerSoundBufferTotal + ((MAXFRAGSIZE * 1))),
   (void *)(mixerSoundBufferTotal + ((MAXFRAGSIZE * 2))),
   (void *)(mixerSoundBufferTotal + ((MAXFRAGSIZE * 3))),
   (void *)(mixerSoundBufferTotal + ((MAXFRAGSIZE * 4))),
   (void *)(mixerSoundBufferTotal + ((MAXFRAGSIZE * 5))),
   (void *)(mixerSoundBufferTotal + ((MAXFRAGSIZE * 6))),
   (void *)(mixerSoundBufferTotal + ((MAXFRAGSIZE * 7))),
   (void *)(mixerSoundBufferTotal + ((MAXFRAGSIZE * 8))),
   (void *)(mixerSoundBufferTotal + ((MAXFRAGSIZE * 9)))};


void mixerInit(unsigned int samplerate, unsigned short bits, unsigned char channels, unsigned char volume)
{
	// The mixer only supports 16 bits right now!
	// If you try to use it with something else, horrible things might happen.

	sprintf(_lastError, "No Error");
	waveout_args woOpen;

	mixerSampleRate = samplerate;
	mixerBits = bits;
	mixerChannels = channels;
	mixerVolume = volume;

	woOpen.sample_rate = samplerate;
	woOpen.format = bits;
	woOpen.channel = channels;
	woOpen.volume = volume;

	woHandle = waveout_open(&woOpen);

	mixerStopAllSound();
	
	curReadBuffer = 0;
	curWriteBuffer = 0;
	soundThreadExit = 0;
	soundThreadPause = 1;

	OSTaskCreate(_mixerPlay, (void *) 0, (void *)&TaskStartStk[TASK_STK_SIZE - 1], TASK_START_PRIO);
	mixerStarted = true;
}

void mixerClose()
{
	if (!mixerStarted)
		return;

	soundThreadPause = 1;
	soundThreadExit = 1;
	mixerStopAllSound();

	while (soundThreadExit == 1);  // to avoid pb with waveout_close -> can hang ??

	mixerOggClear();

	waveout_close(woHandle);
}

void mixerPause(bool b)
{
	soundThreadPause = b ? 1 : 0;
}

bool mixerIsPaused()
{
	return soundThreadPause;
}

int8_t mixerGetVolume()
{
	return mixerVolume;
}

void _mixerSetVolume(int vol)
{
	mixerVolume = vol;

#if defined(GEMEI_A330)
	SYSSetVolume(mixerVolume);
#else
	waveout_set_volume(mixerVolume);
#endif
}

void mixerSetVolume(int8_t v)
{
	if (v < 0)
		mixerVolume = 0;
	else if (v > VOLUME_MAX)
		mixerVolume = VOLUME_MAX;
	else
		mixerVolume = v;

	_mixerSetVolume(mixerVolume);
}

void mixerVolumeIncrease()
{
	if (mixerVolume < VOLUME_MAX)
		mixerVolume += VOLUME_STEP;

	if (mixerVolume > VOLUME_MAX)
		mixerVolume = VOLUME_MAX;

	_mixerSetVolume(mixerVolume);
}

void mixerVolumeDecrease()
{
	if (mixerVolume > 0)
		mixerVolume -= VOLUME_STEP;

	if (mixerVolume < 0)
		mixerVolume = 0;

	_mixerSetVolume(mixerVolume);
}

void mixerSetSampleRate(uint16_t rate)
{
	if (!mixerStarted)
		return;

	pcm_ioctl(PCM_SET_SAMPLE_RATE, rate);
}

void mixerSetChannels(uint8_t channels)
{
	if (!mixerStarted)
		return;

	pcm_ioctl(PCM_SET_CHANNEL, channels);
}

void mixerSetBits(uint8_t bits)
{
	if (!mixerStarted)
		return;

	pcm_ioctl(PCM_SET_FORMAT, bits);
}

void mixerStopAllSound()
{
	if (!mixerStarted)
		return;

	memset(mixerSoundBufferTotal, 0, MAXFRAGSIZE * SOUNDBUFFERS);

	mixerOggClear();

	ps.buffer = NULL;
	ps.size = 0;
	ps.index = 0;
	ps.repeat = false;
}

void _mixerPlay(void *none)
{
	do
	{
		if (soundThreadPause == 0)
		{
			curReadBuffer++;
			if (curReadBuffer == SOUNDBUFFERS)
				curReadBuffer = 0;

			waveout_write(woHandle, mixerSoundBuffers[curReadBuffer], MAXFRAGSIZE);
		}
		else
		{
			OSTimeDly(1);
		}
	} while (soundThreadExit == 0); // Until the end of the sound thread

	soundThreadExit = 0;

	OSTaskDel(TASK_START_PRIO);
}

void mixerSetSound(void* buffer, unsigned int size, bool repeat)
{
	if (!mixerStarted)
		return;

	ps.buffer = buffer;
	ps.size = size;
	ps.index = 0;
	ps.repeat = repeat;
}

void mixerOggClear()
{
	if (!mixerStarted)
		return;

	oggEOF = true;

	if (oggFile != NULL)
	{
		ov_clear(oggFile);
		free(oggFile);
		oggFile = NULL;
	}

	if (oggFileHandle != NULL)
	{
		fclose(oggFileHandle);
		oggFileHandle = NULL;
	}
}

bool mixerSetOgg(char* file)
{
	if (!mixerStarted)
		return false;

	oggEOF = true;
	oggSongLength = 0;
	oggSongCur = 0;

	if (oggFile != NULL)
	{
		ov_clear(oggFile);
		free(oggFile);
		oggFile = NULL;
	}

	if (oggFileHandle != NULL)
	{
		fclose(oggFileHandle);
		oggFileHandle = NULL;
	}

	oggFileHandle = fopen(file, "rb");
	if (oggFileHandle == NULL)
	{
		sprintf(_lastError, "Cannot open file. Bad file name?");
		return false;
	}

	oggFile = (OggVorbis_File*)malloc(sizeof(OggVorbis_File));
	if (oggFile == NULL)
	{
		fclose(oggFileHandle);
		oggFileHandle = NULL;
		sprintf(_lastError, "Out of memory!");
		return false;
	}

	if (ov_open(oggFileHandle, oggFile, NULL, 0) < 0)
	{
		// error
		free(oggFile);
		oggFile = NULL;
		fclose(oggFileHandle);
		oggFileHandle = NULL;
		sprintf(_lastError, "Decoder doesn't want to read the file. Invalid?");
		return false;
	}

	// Verify rate and channels
	oggInfo = ov_info(oggFile, -1);
	if (oggInfo->channels != mixerChannels || oggInfo->rate != mixerSampleRate)
	{
		// error
		free(oggFile);
		oggFile = NULL;
		fclose(oggFileHandle);
		oggFileHandle = NULL;
		sprintf(_lastError, "Sample rate or channels not compatible with current settings");
		return false;
	}

	oggSongLength = ov_time_total(oggFile, -1);

	oggEOF = false;
	return true;
}

bool mixerSetOggFromBuffer(unsigned char* buffer, int size)
{
	if (!mixerStarted)
		return false;

	oggEOF = true;
	oggSongLength = 0;
	oggSongCur = 0;

	if (oggFile != NULL)
	{
		ov_clear(oggFile);
		free(oggFile);
		oggFile = NULL;
	}

	if (oggFileHandle != NULL)
	{
		fclose(oggFileHandle);
		oggFileHandle = NULL;
	}

	oggFile = (OggVorbis_File*)malloc(sizeof(OggVorbis_File));
	if (oggFile == NULL)
	{
		sprintf(_lastError, "Out of memory!");
		return false;
	}

	if (ov_open_buffer(oggFile, buffer, size) < 0)
	{
		// error
		free(oggFile);
		oggFile = NULL;
		sprintf(_lastError, "Decoder doesn't want to read the file. Invalid?");
		return false;
	}

	// Verify rate and channels
	oggInfo = ov_info(oggFile, -1);
	if (oggInfo->channels != mixerChannels || oggInfo->rate != mixerSampleRate)
	{
		// error
		free(oggFile);
		oggFile = NULL;
		sprintf(_lastError, "Sample rate or channels not compatible with current settings");
		return false;
	}

	oggSongLength = ov_time_total(oggFile, -1);

	oggEOF = false;
	return true;
}

bool mixerUpdate()
{
	if (!mixerStarted)
		return false;

	while (curWriteBuffer != curReadBuffer)
	{
		_mixerUpdateOgg();
		_mixerUpdateRaw();

		curWriteBuffer++;
		curWriteBuffer = curWriteBuffer % SOUNDBUFFERS;
	}

	return !oggEOF;
}

void _mixerUpdateOgg()
{
	if (!mixerStarted)
		return;

	char* curBufferPtr = mixerSoundBuffers[curWriteBuffer];
	int curBufferLengthLeft = MAXFRAGSIZE;

	if (!oggEOF)
	{
		oggSongCur = ov_time_tell(oggFile);

		bool curBufferDone = false;
		do {
			if (oggBufferUnWritten > 0)
			{
				// First fill with data from last read
				char * ptr = oggBuffer + oggBufferLastWriteLength - oggBufferUnWritten;
				memcpy(mixerSoundBuffers[curWriteBuffer], ptr, oggBufferUnWritten);

				curBufferPtr += oggBufferUnWritten;
				curBufferLengthLeft = MAXFRAGSIZE - oggBufferUnWritten;

				oggBufferUnWritten = 0;
				oggBufferLastWriteLength = 0;

				if (curBufferLengthLeft == 0)
					break;
			}

			long ret = ov_read(oggFile, oggBuffer, MAXFRAGSIZE, &oggCurrentSection);

			if (ret == 0)
			{
				// EOF
				oggEOF = true;
				curBufferDone = true;
			}
			else if (ret < 0)
			{
				//dataLength[curWriteBuffer] = 0;
				//continue;
				// error in the stream.
			}
			else
			{
				if (ret >= curBufferLengthLeft)
				{
					memcpy(curBufferPtr, oggBuffer, curBufferLengthLeft);
					oggBufferUnWritten = ret - curBufferLengthLeft;
					curBufferDone = true;
					oggBufferLastWriteLength = ret;
				}
				else
				{
					memcpy(curBufferPtr, oggBuffer, ret);
					curBufferPtr += ret;
					curBufferLengthLeft -= ret;
				}
			}
		} while (!curBufferDone);
	}
	
	if (oggEOF) // Not an else, since eof could be set above
	{
		if (curBufferLengthLeft > 0)
			memset(curBufferPtr, 0, curBufferLengthLeft);

		// repeat
		if (oggFile != NULL)
		{
			if (ov_time_seek(oggFile, 0) == 0)
			{
				oggEOF = false;
			}
		}
	}
}

void _mixerUpdateRaw()
{
	if (!mixerStarted)
		return;

	int16_t *pRaw = (ps.buffer + ps.index);
	int16_t *pDest = mixerSoundBuffers[curWriteBuffer];
	if (ps.buffer != NULL)
	{
		if (ps.index < ps.size)
		{
			uint32_t left = (ps.size - ps.index);
			uint32_t data = min(left, MAXFRAGSIZE);

			// If bits = 16
			data = data / 2;
			uint32_t i;
			for (i = 0; i < data; i++, pRaw++, pDest++)
			{
				if (*pRaw != 0)
				{
					if (*pRaw >= 0 && *pDest >= 0)
					{
						*pDest = *pRaw + *pDest - (*pRaw * *pDest / 32768);
					}
					else if (*pRaw < 0 && *pDest < 0)
					{
						*pDest = *pRaw + *pDest + (*pRaw * *pDest / 32768);
					}
					else // different sign
					{
						*pDest = *pRaw + *pDest;
					}
				}
			}

			ps.index += MAXFRAGSIZE;
		}
	}
}

char* mixerLastError()
{
	return _lastError;
}

#endif
